/*
 * Copyright (C) 2012 Sony Computer Entertainment Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 *
 */

#include "config.h"
#include "FontStreamBufferManx.h"

#include <manx/FontData.h>
#include <wtf/FastMalloc.h>
#include <wtf/HashMap.h>
#include <wtf/text/StringHash.h>

// Use 4KiB block size.
#define CACHE_BLOCK_SIZE_SHIFT 12
#define CACHE_BLOCK_SIZE (1 << CACHE_BLOCK_SIZE_SHIFT)
#define CACHE_BLOCK_MASK (CACHE_BLOCK_SIZE - 1)

#define CACHE_BLOCK_ALIGNMENT_SHIFT 6
#define CACHE_BLOCK_ALIGNMENT (1 << CACHE_BLOCK_ALIGNMENT_SHIFT)
#define CACHE_BLOCK_ALIGNMENT_MASK (CACHE_BLOCK_ALIGNMENT - 1)

using namespace WTF;

namespace WebCore {

static FontStreamBuffer::BufferInfo *findBuffer(unsigned blockOffset, const FontStreamBuffer::BufferInfoList &bufferInfoList)
{
    ASSERT(!bufferInfoList.isEmpty());

    FontStreamBuffer::BufferInfo *info;
    for (info = bufferInfoList.head(); info; info = info->next()) {
        if (info->contentSize() && info->offset() == blockOffset)
            return info;
    }

    return 0;
}

static FontStreamBuffer::BufferInfo* findLastRecentlyUsedBuffer(FontStreamBuffer::BufferInfoList &bufferInfoList)
{
    ASSERT(!bufferInfoList.isEmpty());

    return bufferInfoList.tail();
}

static void touchBuffer(FontStreamBuffer::BufferInfo* info, FontStreamBuffer::BufferInfoList &bufferInfoList)
{
    bufferInfoList.remove(info);
    bufferInfoList.push(info);
}

FontStreamBuffer::FontStreamBuffer(SeekFunc seekFunc, ReadFunc readFunc, CloseFunc closeFunc, DestroyFunc destroyFunc, size_t fileSize, void *handle)
    : m_seekFunc(seekFunc)
    , m_readFunc(readFunc)
    , m_closeFunc(closeFunc)
    , m_destroyFunc(destroyFunc)
    , m_handle(handle)
    , m_fileSize(fileSize)
    , m_attachCount(0)
    , m_bufferBaseAddr(0)
{
    ASSERT(seekFunc);
    ASSERT(readFunc);
    ASSERT(closeFunc);
    ASSERT(fileSize > 0);

    size_t allocSize = CACHE_BLOCK_SIZE * NumOfBuffers;
    allocSize += CACHE_BLOCK_ALIGNMENT;

    TryMallocReturnValue result = tryFastMalloc(allocSize);
    if (result.getValue(m_bufferBaseAddr)) {
        // Initialize buffer information.
        unsigned char *baseAddr = (unsigned char*)((uintptr_t)m_bufferBaseAddr & ~CACHE_BLOCK_ALIGNMENT_MASK) + CACHE_BLOCK_ALIGNMENT;
        for (int i = 0; i < NumOfBuffers; ++i) {
            m_bufferInfo[i].setData(baseAddr);
            m_bufferInfo[i].setOffset(0);
            m_bufferInfo[i].setContentSize(0);
            baseAddr += CACHE_BLOCK_SIZE;

            m_bufferInfoList.append(&m_bufferInfo[i]);
        }
    }
}

FontStreamBuffer::~FontStreamBuffer()
{
    fastFree(m_bufferBaseAddr);
}

int FontStreamBuffer::read(void* buffer, size_t count, size_t offset)
{
    ASSERT(offset <= m_fileSize);
    if (offset > m_fileSize)
        return 0;

    if (m_bufferInfoList.isEmpty() || count > CACHE_BLOCK_SIZE * NumOfBuffers / 2)
        return rawRead(buffer, count, offset);

    size_t copied = 0;
    unsigned char* dest = (unsigned char*)buffer;
    while (count) {
        size_t blockOffset = offset & ~CACHE_BLOCK_MASK;
        size_t maskedOffset = offset & CACHE_BLOCK_MASK;

        BufferInfo* bufferInfo = findBuffer(blockOffset, m_bufferInfoList);
        if (!bufferInfo) {
            // No suitbale buffer found. Get LRU buffer and fill it.
            bufferInfo = findLastRecentlyUsedBuffer(m_bufferInfoList);
            ASSERT(bufferInfo);

            size_t fillSize = CACHE_BLOCK_SIZE;
            if (fillSize > m_fileSize - blockOffset)
                fillSize = m_fileSize - blockOffset;

            int readCount = rawRead(bufferInfo->data(), fillSize, blockOffset);
            if (readCount < 0)
                return -1;

            if (!readCount)
                break;

            bufferInfo->setOffset(blockOffset);
            bufferInfo->setContentSize(readCount);
        }

        size_t copySize = bufferInfo->contentSize();
        copySize -= maskedOffset;
        if (copySize > count)
            copySize = count;

        memcpy(dest, (unsigned char*)bufferInfo->data() + maskedOffset, copySize);
        touchBuffer(bufferInfo, m_bufferInfoList);

        count -= copySize;
        dest += copySize;
        copied += copySize;
        offset += copySize;

        // A buffer which is not completely filled is a EOF block.
        if (bufferInfo->contentSize() != CACHE_BLOCK_SIZE)
            break;
    }

    return copied;
}

int FontStreamBuffer::rawRead(void *buffer, size_t count, size_t offset)
{
    ASSERT(buffer);
    ASSERT(offset < m_fileSize);
    ASSERT(offset + count <= m_fileSize);

    m_seekFunc(offset, m_handle);
    return m_readFunc(buffer, count, m_handle);
}

void FontStreamBuffer::close()
{
    ASSERT(!m_attachCount);
    m_closeFunc(m_handle);
    
    if (m_destroyFunc)
        m_destroyFunc(m_handle);

    m_seekFunc = 0;
    m_readFunc = 0;
    m_closeFunc = 0;
    m_destroyFunc = 0;
    m_handle = 0;
    m_fileSize = 0;
}

void FontStreamBuffer::attach()
{
    ++m_attachCount;
}

void FontStreamBuffer::detach()
{
    ASSERT(m_attachCount > 0);
    --m_attachCount;
    if (!m_attachCount)
        close();
}

PassRefPtr<FontStreamBuffer> createFontStreamBuffer(const char* fontPath)
{
    Manx::FontStream::Handle handle = Manx::FontStream::create();
    do {
        if (!handle)
            break;

        int openError = Manx::FontStream::open(fontPath, handle);
        if (openError)
            break;

        unsigned fileSize = Manx::FontStream::size(handle);
        if (!fileSize)
            break;

        return adoptRef(new FontStreamBuffer(Manx::FontStream::seek, Manx::FontStream::read, Manx::FontStream::close, Manx::FontStream::destroy, fileSize, handle));
    } while (0);

    Manx::FontStream::destroy(handle);
    return 0;
}

typedef HashMap<String, RefPtr<FontStreamBuffer> > FontStreamBufferMap;
static FontStreamBufferMap *gFontStreamBufferMap = 0;

namespace FontStreamBufferCache {

void init()
{
    if (gFontStreamBufferMap)
        delete gFontStreamBufferMap;
    gFontStreamBufferMap = new FontStreamBufferMap;
}

WTF::PassRefPtr<FontStreamBuffer> get(const char* fontPath)
{
    // Check detached stream buffer first.
    FontStreamBufferMap::iterator it;
    for (it = gFontStreamBufferMap->begin(); it != gFontStreamBufferMap->end(); ++it) {
        if (!it->value->isAttached())
            gFontStreamBufferMap->remove(it);
    }

    RefPtr<FontStreamBuffer> streamBuffer;
    it = gFontStreamBufferMap->find(fontPath);
    if (it == gFontStreamBufferMap->end()) {
        streamBuffer = createFontStreamBuffer(fontPath);

        if (streamBuffer)
            gFontStreamBufferMap->set(fontPath, streamBuffer);
    } else
        streamBuffer = it->value;

    return streamBuffer;
}

}

}
